#ifndef _ARXHELPER_H_
#define _ARXHELPER_H_

#pragma once
#include <vector>
#include <algorithm>

//////////////////////////////////////////////////////////////////////////
//
// ObjectArx project specific Macros
//
#ifndef ARX_ASSERT
#define ARX_ASSERT(expr)				assert( (expr) )

#define ARX_FAILED(res)					( (res)!=Acad::eOk )
#define ARX_SUCCEEDED(res)				( (res)==Acad::eOk )

#define ARX_CHECK_RES(res)				ARX_ASSERT( (res)==Acad::eOk )

#define ARX_CHECK_PTR(p)				ARX_ASSERT( (p)!=NULL )
#define ARX_CHECK_PTR2(p1,p2)			ARX_ASSERT( (p1)!=NULL && (p2)!=NULL )
#define ARX_CHECK_PTR3(p1,p2,p3)		ARX_ASSERT( (p1)!=NULL && (p2)!=NULL && (p3)!=NULL )
#define ARX_CHECK_PTR4(p1,p2,p3,p4)		ARX_ASSERT( (p1)!=NULL && (p2)!=NULL && (p3)!=NULL && (p4)!=NULL)

#endif // ARX_ASSERT



namespace ArxHelper
{

//////////////////////////////////////////////////////////////////////////
// Helper functions
//
#ifndef PI
#define PI		3.1415926535897932384626				// PI
#define PI_1	0.31830988618379067153777192239026		// 1/PI
#endif

#ifndef EPSILON
#define EPSILON (1.0E-4)
#endif

inline bool EqualToZero(double smallNum)
{
	return ((-EPSILON)<smallNum) && (smallNum<EPSILON);
}

inline bool IsEqualDouble(double a,double b)
{
	return EqualToZero(a-b);
}

inline double RadToDeg(double rad)
{
	return rad*180.0*PI_1;		// rad*180/PI
}

inline double DegToRad(double deg)
{
	const double ratio = 1.0/180.0;
	return deg*PI*ratio;		// deg*PI/180
}

//////////////////////////////////////////////////////////////////////////
//
// CAcDbObjectPtr<T>
//
template< typename T >
class CAcDbObjectPtr
{
public:
	T* p;

public:
	CAcDbObjectPtr()
		: p(NULL)
	{}

	CAcDbObjectPtr(const CAcDbObjectPtr<T>& objPtr)
		: p(NULL)
	{
		Attach(objPtr.Detach());
	}

	CAcDbObjectPtr(T* pT)
		: p(NULL)
	{
		Attach(pT);
	}

	T* operator=(T* pT)
	{
		if (*this != pT)
		{
			Attach(pT);
		}
		return *this;
	}

	T* operator=(const CAcDbObjectPtr<T>& objPtr)
	{
		if (*this != objPtr)
		{
			Attach(objPtr.Detach());
		}
		return *this;
	}

	~CAcDbObjectPtr()
	{
		Release();
	}

public:
	void Attach(T* pT)
	{
		ARX_ASSERT(pT!=NULL && p!=pT);
		Release();
		p = pT;
	}

	T* Detach()
	{
		ARX_ASSERT(p!=NULL);
		T* pT = p;
		p = NULL;
		return pT;
	}

	void Release()
	{
		if (p)
		{
			// Every class derived from CAcDbObject has a close() method
			p->close();	
			p = NULL;
		}
	}

	T* operator->() const
	{
		ARX_ASSERT(p!=NULL);
		return p;
	}

	operator const T*()
	{
		return p;
	}

	operator T*() const
	{
		return p;
	}

	operator AcRxObject*()
	{
		// AcRxObject class is the root base class of all AcDbObject class
		return static_cast<AcRxObject*>(p);
	}

	operator T*&()
	{
		// This conversion is only used to serve a return parameter in a function's parameter list,
		// such as : acdbOpenAcDbEntity(AcDbEntity*& pEnt,...), so p must be a NULL pointer
		ARX_ASSERT(p==NULL);
		return p;
	}

	T& operator *() const
	{
		ARX_ASSERT(p!=NULL);
		return *p;
	}

	bool operator<(T* pT) const
	{
		return (p < pT);
	}

	bool operator==(T* pT) const
	{
		return (p == pT);
	}

	bool operator!=(T* pT) const
	{
		return (p != pT);
	}

	bool operator!() const
	{
		return (p == NULL);
	}
};

//////////////////////////////////////////////////////////////////////////
//
// CAdsName
//
inline bool IsValidName(ads_name name)
{
	return !(ads_name_nil(name));
}

class CAdsName
{
public:
	ads_name name;

public:
	CAdsName()
	{
		Clear();
	}

	~CAdsName()
	{}

public:
	bool IsValid()
	{
		return IsValidName(name);
	}

	int EntSel(const ACHAR* str, ads_point ptres)
	{
		return acedEntSel(str,name,ptres);
	}

	int EntSel(const ACHAR* str)
	{
		ads_point ptres;
		return acedEntSel(str,name,ptres);
	}

	Acad::ErrorStatus GetObjectId(__out AcDbObjectId& objId)
	{
		ARX_ASSERT(IsValid());
		return acdbGetObjectId(objId,name);
	}

private:
	void Clear()
	{
		ads_name_clear(name);
	}
};

//////////////////////////////////////////////////////////////////////////
//
// CAcEdSelectionSet
//
class CAcEdSelectionSet
{
public:
	CAcEdSelectionSet()
	{
		Clear();
	}

	CAcEdSelectionSet(ads_name name)
	{
		ARX_ASSERT(IsValidName(name));
		ads_name_set(name,ssname);
	}

	CAcEdSelectionSet& operator=(ads_name name)
	{
		ARX_ASSERT(IsValidName(name));
		Attach(name);
		return *this;
	}

	CAcEdSelectionSet& operator=(CAcEdSelectionSet& acedSS)
	{
		ARX_ASSERT(acedSS.IsValid());
		ads_name name;
		acedSS.Detach(name);
		Attach(name);
		return *this;
	}

	~CAcEdSelectionSet()
	{
		Free();
	}

public:
	bool IsValid()
	{
		return IsValidName(ssname);
	}

	void Attach(ads_name name)
	{
		ARX_ASSERT(IsValidName(name));
		Free();
		ads_name_set(name,ssname);
	}

	void Detach(ads_name name)
	{
		ARX_ASSERT(IsValid() && ads_name_nil(name));
		ads_name_set(ssname,name);
		Clear();
	}

	int Get(const ACHAR* str, 
		const void* pt1, 
		const void* pt2, 
		const struct resbuf* filter)
	{
		return acedSSGet(str,pt1,pt2,filter,ssname);
	}

	int Get(const ACHAR* str, const struct resbuf* filter)
	{
		return acedSSGet(str,NULL,NULL,filter,ssname);
	}

	int GetNewSelect()
	{
		return acedSSGet(NULL,NULL,NULL,NULL,ssname);
	}

	int GetPickfirst()
	{
		return acedSSGet(_T("I"),NULL,NULL,NULL,ssname);
	}

	/*
	int Add(const ads_name ename,ads_name result)
	{
		return acedSSAdd(ename,ssname,result);
	}
	*/

	int Length(long* length)
	{
		ARX_ASSERT(IsValid());
		return acedSSLength(ssname,length);
	}

	int NameAt(long i,ads_name name)
	{
		ARX_ASSERT(IsValid());
		return acedSSName(ssname,i,name);
	}

	int NameAt(long i,CAdsName& adsname)
	{
		ARX_ASSERT(IsValid());
		return acedSSName(ssname,i,adsname.name);
	}

	int Free()
	{
		if (IsValid())
		{
			ads_name name;
			ads_name_set(ssname,name);
			Clear();
			return acedSSFree(name);
		}
		return RTNORM;
	}

private:
	void Clear()
	{
		ads_name_clear(ssname);
	}

public:
	ads_name ssname;
};

Acad::ErrorStatus SplitCurveWithPoints(
	__in const AcDbObjectId& curveId,
	__in const std::vector<AcGePoint3d>& vecPoint,
	__in_opt bool eraseOrigCurve = false )
{
	ARX_ASSERT(!curveId.isNull());
	ARX_ASSERT(vecPoint.size()>0);

	CAcDbObjectPtr<AcDbEntity> spEnt;
	acdbOpenAcDbEntity(spEnt,curveId,kForWrite);
	ARX_CHECK_PTR(spEnt);
	if ( !(spEnt->isKindOf(AcDbLine::desc()) 
		|| spEnt->isKindOf(AcDbArc::desc())
		|| spEnt->isKindOf(AcDbCircle::desc())))	// only line, arc and circle is supported to be break
	{
		return eInvalidInput;
	}

	Acad::ErrorStatus res = Acad::eOk;

	AcDbCurve* pCurve = AcDbCurve::cast(spEnt);
	ARX_CHECK_PTR(pCurve);

	// get split parameters
	std::vector<double> vecParam;
	for (std::vector<double>::size_type i=0;i<vecPoint.size();i++)
	{
		double param = 0.0;
		res = pCurve->getParamAtPoint(vecPoint[i],param);
		if (ARX_FAILED(res))	// skip if this point is not on the curve
			continue;;

		bool hasEqualValue = false;
		for (std::vector<double>::iterator it=vecParam.begin();it!=vecParam.end();it++)
		{
			if (IsEqualDouble(param,*it))
			{
				hasEqualValue = true;
				break;
			}
		}
		if (!hasEqualValue)
			vecParam.push_back(param);
	}
	if (vecParam.size()<1)
		return Acad::PointNotOnEntity;

	//
	std::sort(vecParam.begin(),vecParam.end());

	//
	AcGeDoubleArray paramArray;
	for (std::vector<double>::iterator it=vecParam.begin();it!=vecParam.end();it++)
	{
		paramArray.append(*it);
	}

	// get spite curves
	AcDbVoidPtrArray lineSegArray;
	pCurve->getSplitCurves(paramArray,lineSegArray);

	//
	CAcDbObjectPtr<AcDbBlockTable> spBT;
	acdbHostApplicationServices()->workingDatabase()->getBlockTable(spBT,AcDb::kForRead);
	CAcDbObjectPtr<AcDbBlockTableRecord> spBTR;
	spBT->getAt(ACDB_MODEL_SPACE,spBTR,AcDb::kForWrite);
	for (int i=0;i<lineSegArray.length();i++)
	{
		CAcDbObjectPtr<AcDbCurve> spCurveNew = AcDbCurve::cast(static_cast<AcRxObject*>(lineSegArray[i]));
		ARX_CHECK_PTR(spCurveNew);
		if (spCurveNew != NULL)
		{
			// append the newly created entity into database
			res = spBTR->appendAcDbEntity(spCurveNew.p);
			ARX_CHECK_RES(res);
		}
	}

	if (eraseOrigCurve)
	{
		res = pCurve->erase();	// erase the original curve
		ARX_CHECK_RES(res);
	}
	return res;
}

Acad::ErrorStatus SplitCurveWithEntity(
	__in const AcDbObjectId& curveId,
	__in const std::vector<AcDbObjectId>& vecEntId,
	__in_opt bool eraseOrigCurve = false )
{
	ARX_ASSERT(!curveId.isNull());
	ARX_ASSERT(vecEntId.size()>0);

	CAcDbObjectPtr<AcDbEntity> spEnt;
	acdbOpenAcDbEntity(spEnt,curveId,kForWrite);
	ARX_CHECK_PTR(spEnt);
	if ( !(spEnt->isKindOf(AcDbLine::desc()) 
		|| spEnt->isKindOf(AcDbArc::desc())
		|| spEnt->isKindOf(AcDbCircle::desc())))	// only line, arc and circle is supported to be split
	{
		return Acad::eInvalidInput;
	}

	AcDbCurve* pCurve = AcDbCurve::cast(spEnt);
	ARX_CHECK_PTR(pCurve);

	AcGePoint3d startPt, endPt;
	pCurve->getStartPoint(startPt);
	pCurve->getEndPoint(endPt);

	std::vector<AcGePoint3d> vecPoint;
	for (std::vector<AcDbObjectId>::const_iterator it=vecEntId.begin();it!=vecEntId.end();it++)
	{
		if ((*it) == curveId)	// skip if (*it) and curveId are the same object
			continue;

		CAcDbObjectPtr<AcDbEntity> spEntIntersect;
		acdbOpenAcDbEntity(spEntIntersect,*it,AcDb::kForRead);
		ARX_CHECK_PTR(spEntIntersect);

		AcGePoint3dArray arrPt;
		pCurve->intersectWith(spEntIntersect,AcDb::kOnBothOperands,arrPt);
		for (int i=0;i<arrPt.length();i++)
		{
			AcGePoint3d& pt = arrPt[i];
			// skip if pt is pCurve's start or end point
			if (pt.isEqualTo(startPt) || pt.isEqualTo(endPt))
				continue;

			vecPoint.push_back(pt);
		}
	}
	// close the curve manually, 
	// because SplitCurveWithPoints() function below need to open it for write
	spEnt.Release();

	if (vecPoint.size()==0)
	{
		return Acad::HasNoIntersectPoint;
	}
	else
	{
		Acad::ErrorStatus res = SplitCurveWithPoints(curveId,vecPoint,eraseOrigCurve);
		ARX_ASSERT(res!=Acad::PointNotOnEntity);
		return res;
	}
}

class CAcEd
{
public:
	enum CAcEdErrorCode
	{
		OK = RTNORM,
		Cancle = RTCAN,
		None = RTNONE
	};

public:
	static int InitGet(
		int val,
		const ACHAR* kwl = NULL)
	{
		return acedInitGet(val,kwl);
	}

	static int GetEntity(
		__in const ACHAR* prompt, 
		__out AcDbObjectId& entId)
	{
		CAdsName name;
		int res = name.EntSel(prompt);
		if (res == RTNORM)
			name.GetObjectId(entId);
		return res;
	}

	static int GetPickfirstEntities(__out std::vector<AcDbObjectId>& vecEntId)
	{
		ARX_ASSERT(vecEntId.size()==0);

		CAcEdSelectionSet ss;
		int res = ss.GetPickfirst();
		if (res == RTNORM)
		{
			GetEnitiesFromSelectionSet(ss,vecEntId);			
		}
		return res;
	}

	static int GetMultiEntities(
		__in const ACHAR* prompt, 
		__out std::vector<AcDbObjectId>& vecEntId,
		__in_opt bool getPickfirst = true )
	{
		ARX_ASSERT(vecEntId.size()==0);

		acedPrompt(prompt);
		int res = RTCAN;
		if (getPickfirst)
		{
			res = GetPickfirstEntities(vecEntId);
		}

		if (res != RTNORM)
		{
			CAcEdSelectionSet ss;
			res = ss.GetNewSelect();
			if (res != RTNORM)
				return res;

			GetEnitiesFromSelectionSet(ss,vecEntId);
		}
		return res;
	}

	static void GetEnitiesFromSelectionSet(
		__in CAcEdSelectionSet& ss,
		__out std::vector<AcDbObjectId>& vecEntId )
	{
		ARX_ASSERT(ss.IsValid() && vecEntId.size()==0);

		long length = 0;
		ss.Length(&length);
		ARX_ASSERT(length>0);
		for (long i=0;i<length;i++)
		{
			CAdsName name;
			ss.NameAt(i,name);
			AcDbObjectId id;
			name.GetObjectId(id);
			ARX_ASSERT(!id.isNull());

			vecEntId.push_back(id);
		}
	}

	static int GetPoint(
		__in const ACHAR* prompt,
		__inout AcGePoint3d& point,
		__in_opt bool useDefaultValue = false)
	{
		int res = RTCAN;
		ads_point pt;
		if (useDefaultValue)
		{
			ATL::CString promptStr;
			promptStr.Format(_T("%s < (%1.3f, %1.3f) >"),prompt,point.x,point.y);
			res = acedGetPoint(NULL,promptStr,pt);
			if (res == RTNORM)
			{
				point.set(pt[X],pt[Y],pt[Z]);
			}
			else if (res == RTNONE)		// user accept the default value that is specified with "point"
			{
				res = RTNORM;
			}
		}
		else
		{
			res = acedGetPoint(NULL,prompt,pt);
			if (res == RTNORM)
			{
				point.set(pt[X],pt[Y],pt[Z]);
			}
		}
		return res;
	}

	static int GetMultiPoints(
		__in const ACHAR* prompt,
		__out std::vector<AcGePoint3d>& vecPoint )
	{
		int res = RTCAN;
		for (;;)
		{
			ads_point pt;
			res = acedGetPoint(NULL,prompt,pt);
			if (res != RTNORM)
			{
				if (res == RTNONE)	// the user hit "enter" or "space bar", indicate the complete of selection
					res = RTNORM;

				break;
			}
			vecPoint.push_back(AcGePoint3d(pt[X],pt[Y],pt[Z]));
		}
		return res;
	}

	static int GetReal(
		__in const ACHAR* prompt,
		__inout double& value,
		__in_opt bool useDefaultValue = false )
	{
		int res = RTCAN;
		double newValue = 0;
		if (useDefaultValue)
		{
			ATL::CString promptStr;
			promptStr.Format(_T("%s < %1.3f >"),prompt,value);
			res = acedGetReal(promptStr,&newValue);
			if (res == RTNORM)
			{
				value = newValue;
			}
			else if (res == RTNONE)		// user accept the default value that is specified with "point"
			{
				res = RTNORM;
			}
		}
		else
		{
			res = acedGetReal(prompt,&newValue);
			if (res == RTNORM)
			{
				value = newValue;
			}
		}
		return res;
	}
};

} // namespace ArxHelper

#endif